/************************
 * Autor: Lukasz Kufel  *
 * Przedmiot: Jezyk D   *
 * zadanie nr 1 "Saper" *
 ************************/

import dfl.all;
import std.stdio;
import std.random;
import std.conv;
import std.string;

const int SQUARE_WIDTH = 16;
const int SQUARE_HEIGHT = 16;
const int BOARD_MARGIN = 5;
const int ROW_MAX = 16;
const int COLUMN_MAX = 16;
const int ALL_MINES = 40;
const int BOARD_WIDTH = SQUARE_WIDTH * COLUMN_MAX + 2 * BOARD_MARGIN;
const int BOARD_HEIGHT = SQUARE_HEIGHT * ROW_MAX + 2 * BOARD_MARGIN; 
const int INFO_WIDTH = BOARD_WIDTH;
const int INFO_HEIGHT = 40;
const int WINDOW_WIDTH = BOARD_WIDTH;
const int WINDOW_HEIGHT = BOARD_HEIGHT + INFO_HEIGHT;

class Point{
	public int x, y;
	
	this(int xx, int yy)
	{
		x = xx;
		y = yy;
	}
}

class Square : PictureBox{
	bool hasMine;
	int mineCounter;
	bool checked;
	bool hasFlag;
	public static Bitmap[int] imageMap;
	
	static this(){
		imageMap[0] = new Bitmap("bmp/0.bmp");
		imageMap[1] = new Bitmap("bmp/1.bmp");
		imageMap[2] = new Bitmap("bmp/2.bmp");
		imageMap[3] = new Bitmap("bmp/3.bmp");
		imageMap[4] = new Bitmap("bmp/4.bmp");
		imageMap[5] = new Bitmap("bmp/5.bmp");
		imageMap[6] = new Bitmap("bmp/6.bmp");
		imageMap[7] = new Bitmap("bmp/7.bmp");
		imageMap[8] = new Bitmap("bmp/8.bmp");
		imageMap[9] = new Bitmap("bmp/8.bmp"); // fiction
		
		imageMap[10] = new Bitmap("bmp/button.bmp");
		imageMap[11] = new Bitmap("bmp/button_down.bmp");
		imageMap[12] = new Bitmap("bmp/button_over.bmp");
		
		imageMap[13] = new Bitmap("bmp/mine2.bmp");
		imageMap[14] = new Bitmap("bmp/mine_kill.bmp");
		
		imageMap[15] = new Bitmap("bmp/flag_wrong.bmp");
		imageMap[16] = new Bitmap("bmp/flag.bmp");
	}
	
	this(bool hMine = false, int mCounter = 0){
		hasMine = hMine;
		mineCounter = mCounter;
		checked = false;
		hasFlag = false;
		image = imageMap[10];
	}
	
	bool mine(){ //read property
		return hasMine;
	}
	
	bool mine(bool value){ //write property
		return hasMine = value;
	}
	
	int counter() {
		return mineCounter;
	}
	
	int counter(int value) {
		return mineCounter = value;
	}
	
	bool flaged(){
		return hasFlag;
	}
	
	bool flaged(bool value){
		if (!checked){
			if (value){
				image = imageMap[16];
			} else {
				image = imageMap[10];
			}
			hasFlag = value;
		}
		return hasFlag;
	}
	
}

class Info : Panel {
	Label lMines;
	Button bNewGame;
	
	this() {
		backColor = Color(255,255,255);
		borderStyle = BorderStyle.FIXED_SINGLE;
		bounds = Rect(0, 0, INFO_WIDTH, INFO_HEIGHT);
		
		lMines = new Label();
		lMines.text = std.conv.toString(ALL_MINES);
		lMines.backColor = Color(0,0,0);
		lMines.foreColor = Color(255,0,0);
		lMines.bounds = Rect(0, 0, INFO_WIDTH/3, INFO_HEIGHT);
		lMines.textAlign = ContentAlignment.MIDDLE_CENTER;
		lMines.font = new Font("Verdana", 24f, FontStyle.BOLD);
		lMines.parent = this;
		
		bNewGame = new Button();
		bNewGame = new dfl.button.Button();
		//bNewGame.name = "btnNewGame";
		bNewGame.text = "Nowa Gra";
		//bNewGame.dock = dfl.all.DockStyle.FILL;
		bNewGame.bounds = Rect(lMines.width, 0, 2 * INFO_WIDTH/3, INFO_HEIGHT);
		bNewGame.parent = this;
		bNewGame.click ~= &newGame;
	}
	
	void newGame(Control sender, EventArgs ea){
		updateInfo(0);
		(cast(SaperWindow)parent).newGame();
	}
	
	void updateInfo(int puttedFlags){
		lMines.text = std.conv.toString(ALL_MINES - puttedFlags);
	}
}

class Board : Panel {
	
	Square[ROW_MAX][COLUMN_MAX] table;
	bool gameOver;
	int squaresToCheck;
	int puttedFlags;
	
	this(){
		//layout
		backColor = Color(50,50,50);
		borderStyle = BorderStyle.NONE;
		bounds = Rect(0, INFO_HEIGHT, BOARD_WIDTH, BOARD_HEIGHT);
		
		squaresToCheck = ROW_MAX * COLUMN_MAX - ALL_MINES;
		puttedFlags = 0;
		Square s;
		for (int j = 0; j < ROW_MAX; j++) {
			for (int i = 0; i < COLUMN_MAX; i++) {
				s = new Square;
				s.name = std.conv.toString(i) ~ "." ~ std.conv.toString(j);
				//s.mouseEnter ~= &mEnter;
				//s.mouseLeave ~= &mLeave;
				//s.mouseDown ~= &mDown;
				s.mouseUp ~= &mUp;
				s.bounds = Rect(i*SQUARE_WIDTH + BOARD_MARGIN, j*SQUARE_HEIGHT + BOARD_MARGIN, SQUARE_WIDTH, SQUARE_HEIGHT);
				s.parent = this;
				table[i][j] = s;
			}
		}
		
		gameOver = false;
		generateTable();
		//showAllMines();
	}
	
	private void resetBoard(){
		for (int j = 0; j < ROW_MAX; j++) {
			for (int i = 0; i < COLUMN_MAX; i++) {
				table[i][j].mine = false;
				table[i][j].checked = false;
				table[i][j].flaged = false;
				table[i][j].counter = 0;
				table[i][j].image = Square.imageMap[10];
			}
		}
		squaresToCheck = ROW_MAX * COLUMN_MAX - ALL_MINES;
		puttedFlags = 0;
		gameOver = false;
	}
	
	private void generateTable(){
		int mines;
		
		do{
			for (int j = 0; j < ROW_MAX; j++) {
				for (int i = 0; i < COLUMN_MAX; i++) {
					if (mines < ALL_MINES) {
						if (rand() % 100 > 95) {
							if (!table[i][j].mine) {
								table[i][j].mine = true;
								updateTable(i, j);
								mines++;
							}
						}
					}
				}
			}
		} while (mines < ALL_MINES);
	}
	
	private void updateTable(int x, int y){
		
		for (int i=-1; i <= 1; i++){
			for (int j=-1; j <= 1; j++){
				if (x+i >= 0 && x+i < COLUMN_MAX && y+j >= 0 && y+j < ROW_MAX){
					if (!table[x+i][y+j].mine){
						table[x+i][y+j].counter = table[x+i][y+j].counter + 1;
					}
				}
			}
		}
	}
	
	void showAllMines(){
		for (int j = 0; j < ROW_MAX; j++) {
			for (int i = 0; i < COLUMN_MAX; i++) {
				if (table[i][j].mine && !table[i][j].checked) {
					table[i][j].image = Square.imageMap[13];
				}
			}
		}
	}
	
	void showAllSquares(){
		for (int j = 0; j < ROW_MAX; j++) {
			for (int i = 0; i < COLUMN_MAX; i++) {
				if (table[i][j].mine) {
					table[i][j].image = Square.imageMap[13];
				} else {
					table[i][j].image = Square.imageMap[table[i][j].counter];
				}
			}
		}
	}
	
	void checkNeirbours(int x, int y) {
		Point[] toCheck;
		addNeirboursToCheck(toCheck, new Point(x, y));
		for(int i = 0; i < toCheck.length; i++) {
			if (!table[toCheck[i].x][toCheck[i].y].checked) {
				table[toCheck[i].x][toCheck[i].y].image
					= Square.imageMap[table[toCheck[i].x][toCheck[i].y].counter];
				table[toCheck[i].x][toCheck[i].y].checked = true;
				squaresToCheck--;
				if (table[toCheck[i].x][toCheck[i].y].counter == 0) {
					addNeirboursToCheck(toCheck, toCheck[i]);
				}
				
			}
		}
		//debug writefln("toCheck.length =",toCheck.length);
	}
	
	void addNeirboursToCheck(ref Point[] checkTable, Point p){
		for (int i = -1; i <= 1; i++) {
			for (int j = -1; j <= 1; j++) {
				if (i!=0 || j!=0 ){
					Point np = new Point(p.x+i, p.y+j);
					if (p.x+i >= 0 && p.x+i < COLUMN_MAX
							&& p.y+j >= 0 && p.y+j < ROW_MAX
							&& !table[p.x+i][p.y+j].checked
							&& !table[p.x+i][p.y+j].flaged){
						checkTable ~= np;
					}
				}
			}
		}
	}
	
	void newGame(){
		//printTable();
		resetBoard();
		generateTable();
	}
	
	private void printTable(){
		for (int j = 0; j < ROW_MAX; j++) {
			for (int i = 0; i < COLUMN_MAX; i++) {
				if (table[i][j].checked || table[i][j].mine) {
					writef("x ");
				} else {
					writef("o ");
				}
			}
			writefln("");
		}
	}
	/*private void mEnter(Control sender, MouseEventArgs ea) {
		if (!gameOver) {
			Square s = cast(Square)sender;
			if (!s.checked) {
				s.image = s.imageMap[12];
			}
		}
	}
	
	private void mLeave(Control sender, MouseEventArgs ea) {
		if (!gameOver) {
			Square s = cast(Square)sender;
			if (!s.checked) {
				s.image = s.imageMap[10];
			}
		}
	}
	
	private void mDown(Control sender, MouseEventArgs ea) {
		if (!gameOver) {
			Square s = cast(Square)sender;
			if (!s.checked && !s.flaged) {
				s.image = s.imageMap[0];
			}
		}
	}*/
	
	private void mUp(Control sender, MouseEventArgs ea) {
		if (!gameOver) {
			Square s = cast(Square)sender;
			
			if(ea.button == MouseButtons.RIGHT) {
				if (!s.flaged && puttedFlags < ALL_MINES) {
					s.flaged = true;
					puttedFlags++;
				} else if (s.flaged){
					s.flaged = false;
					puttedFlags--;
				}
				(cast(SaperWindow)parent).updateInfo(puttedFlags);
			} else if(ea.button == MouseButtons.LEFT) {
				if (!s.checked && !s.flaged) {
					
					if (s.mine) {
						s.image = s.imageMap[14];
						gameOver = true;
						showAllMines();
						msgBox("Przegrales", "Info", MsgBoxButtons.OK, MsgBoxIcon.INFORMATION);
					} else {
						s.image = s.imageMap[s.counter];
						if (s.counter == 0) {
							char[][] point = split(s.name,".");
							//writefln(point);
							checkNeirbours(toInt(point[0]), toInt(point[1]));
						} else {
							squaresToCheck--;
						}
					}
					s.checked = true;
				}
				//writefln(squaresToCheck);
				if (!squaresToCheck) {
					msgBox("Wygrales", "Info", MsgBoxButtons.OK, MsgBoxIcon.INFORMATION);
					gameOver = true;
				}
			}
		}
	}
}

class SaperWindow : Form {
	Board board;
	Info info;
	this(){
		//Panels
		board = new Board;
		board.parent = this;
		info = new Info;
		info.parent = this;
		
		//SaperWindow
		text = "Saper -- ver0.5 niegrywalna";
		maximizeBox = false;
		formBorderStyle = FormBorderStyle.FIXED_SINGLE;
		clientSize = Size(WINDOW_WIDTH, WINDOW_HEIGHT);
		startPosition = FormStartPosition.CENTER_SCREEN;
	}
	
	void newGame(){
		board.newGame();
	}
	
	void updateInfo(int puttedFlags){
		info.updateInfo(puttedFlags);
	}
}

int main(char[][] args){
	int result = 0;
	
	try {
		Application.run(new SaperWindow);
	} catch(Object o) {
		msgBox(o.toString(), "Fatal Error", MsgBoxButtons.OK, MsgBoxIcon.ERROR);
		result = 1;
	}
	return result;
}